package serverClient;

import sync.FileInfo;
import sync.FileList;
import gui.GuiTreeSelectFiles;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.concurrent.Semaphore;

public class Client extends ServerClient {
	
	private final boolean DEBUG = false;
	
	// Variables for the checksum thread
	private Thread checksumThread; // The thread that counts all the checksums
	private Semaphore startChecksumSemaphore; // Semaphore to start the count of one checksum
	private Semaphore checksumDoneSemaphore; // Semaphore to mark that a checksum is ready
	private boolean allChecksumsDone; // Boolean to finish the checksum thread when all checksums are done
	private String filePathChecksum; // Variable for the checksum thread of the current file
	private long checksum; // Variable for the checksum thread of the current checksum value
	
	
	public Client(String confDir, BufferedInputStream inBufStream, BufferedOutputStream outBufStream, File workDir, boolean allowDelete, boolean deleteWarning, boolean includeHidden, String syncMode, boolean useExactTime) {
		
		super(confDir, inBufStream, outBufStream, workDir, allowDelete, deleteWarning, includeHidden, syncMode, useExactTime);
	}
	
	
	// Runs the client
	// Calls different methods to go through the protocol
	public boolean runClient() {
		
		boolean ranSuccessfully = true; // Is the return value of the function
		try {
			if(!isSecondSyncThread) sendTranferMessage("Gathering data. This may take several minutes..."); // Sending message to observer
			FileList ourFL = createOurFileList();
			FileList remoteFL = receiveRemoteFileList();
			sendOurFileList();
			boolean isSynced = checkIfSynced(ourFL, remoteFL);
			if(!isSynced) {
				if(allowDelete && syncMode.equals("dual")) { // Runs this if delete is allowed, depends on state
					FileList deleteFL = createDeleteFileList(remoteFL);
					deleteFL = handleDeleteFileList(deleteFL, ourFL);
					if(deleteWarning && deleteFL.getSize() > 0) {
						GuiTreeSelectFiles selectFiles = new GuiTreeSelectFiles(null, true, deleteFL, workDir.getPath());
						deleteFL = selectFiles.toDelete();
						//deleteFL.saveToTextFile(confDir + DELETE_FL + fileNameExtension + ".filel");
					}
					deleteFiles(deleteFL, workDir.getAbsolutePath(), "");
					ourFL = handleOurFileList(ourFL, deleteFL);
				}
				sendOurFileList();
				deleteTheOldTransferFileList(); 
				FileList transferFL = createTransferFileList(ourFL, remoteFL);
				sendTransferFileList();
				sendInfoToGui(transferFL);
				sendFiles(transferFL);
				if(!isSecondSyncThread) sendTranferMessage("Gathering data..."); // Sending message to observer
				remoteFL = receiveRemoteFileList();
			}
			saveOldRemoteFileList(remoteFL);
			deleteUnneededFileLists();
			if(printFinalSyncCheck) finalSyncCheck(isSynced, ourFL, remoteFL);
			
		} catch (Exception e) {
			ranSuccessfully = false;
			e.printStackTrace();
			if(errorMessage.equals("")) {
				sendConnectionWarning("File transfer failed!");
			}
			else {
				sendConnectionWarning(errorMessage);
			}
		}
		
		closeBuffs();
		return ranSuccessfully;
	}
	
	
	// Creates a FileList of files that needs to be sent to the server 
	// and saves the file locally as "transferFileList.filel"
	private FileList createTransferFileList(FileList ourFL, FileList remoteFL) throws Exception {
		
		String path = confDir + TRANSFER_FL + fileNameExtension + ".filel";
		FileList transferFL;
		if(syncMode.equals("backup")) {
			transferFL = ourFL.compareFileList(remoteFL, true);
		}
		else {
			transferFL = ourFL.compareFileList(remoteFL, false);
		}
		try {
			transferFL.saveToTextFile(path);
		} catch (FileNotFoundException e) {
			errorMessage = "Could not store a needed data file!";
			throw new Exception();
		} catch (Exception e) {
			errorMessage = "System can not write a needed file!";
			throw new Exception();
		}
		
		return transferFL;
	}
	
	// Method that sends all the the files from the transferFileList to the client
	// ignores directory entry's in the transferFileList
	private void sendFiles(FileList transferFileList) throws Exception {
		
		// Amount of files to be sent plus directory's
		int noOfFiles = transferFileList.getSize();
		FileInfo temp;
		
		// Starts the thread that counts the checksums
		allChecksumsDone = false;
		startChecksumThread();
		
		for(int i=0; i < noOfFiles; i++) {
			if(!isSecondSyncThread) fileInProgress(); // Is for updating which file is being processed to the progress bar
			
			temp = transferFileList.get(i);
			// Directory control
			String fileName = temp.getName();
			char dirSymbol = fileName.charAt(fileName.length()-1); // Gets the last symbol
			// Sends the file from the transferFileList, ignores map entries (represented as '/')
			if(dirSymbol != '/') {
				if(!isSecondSyncThread) sendTranferMessage("Send: "  + fileName); // Sending message to observer
				String filePath = getFilePath(temp);
				filePathChecksum = filePath; // A copy to the checksum thread
				startChecksumSemaphore.release();
				sendFile(filePath);
				checksumDoneSemaphore.acquire();
				sendChecksum(checksum);
			}
		}
		
		// End the thread that counts the checksums
		allChecksumsDone = true;
		startChecksumSemaphore.release(); // Special case the last time
		checksumThread.join();
		if(DEBUG) System.out.println("Is checksumThread alive? " + checksumThread.isAlive());
		
		if(!isSecondSyncThread) filesTransferFinished(); // Send info to guiStatusBar
	}
	
	// Start a thread that count all checksums
	private void startChecksumThread() {
		
		// Initiates the needed semaphores
		startChecksumSemaphore = new Semaphore(0);
		checksumDoneSemaphore = new Semaphore(0);
		
		checksumThread = new Thread(new Runnable() {
			public void run() {
				while(!allChecksumsDone) {
					try {
						startChecksumSemaphore.acquire();
						if(allChecksumsDone) {
							break;
						}
						// Figures out the check sum for the current file
						checksum = sync.Checksum.createChecksum(filePathChecksum);
					} catch (InterruptedException e) {
						if(DEBUG) System.err.println("Problems with the startChecksum semaphore!");
						e.printStackTrace();
					} catch (Exception e) {
						if(DEBUG) System.err.println("Could not make checksum!");
						e.printStackTrace();
					}
					checksumDoneSemaphore.release();
				}
			}
		});
		checksumThread.start();
	}
	
	// Sends one file to the server through a buffer that is
	private void sendFile(String path) throws Exception {
		
		if(DEBUG) System.out.println("Send: " + path);
		send(path);
	}
	
	private void sendChecksum(long checksum) throws Exception {
		
		// Sends the checksum
		byte[] longByte = longToByteArr(checksum);
		try {
			outBufStream.write(longByte, 0, 8);
			outBufStream.flush();
		} catch (IOException e) {
			errorMessage = "Could not send checksum!";
			throw new Exception();
		}
	}
	
}
